package population;

import java.io.File;
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import utilities.MinMaxPair;
import utilities.TimeProperties;
import utilities.WorldDate;
import utilities.WorldState;
import character.Gender;
import character.NonPlayerCharacter;
import character.Family;
import character.Character;
import character.race.Race;
import character.race.RaceProperties;

/**
 * Construct initial populations.
 * @author pcdejager
 */
public final class InitialPopulationGenerator {
    /**
     * Constructor.
     */
    public InitialPopulationGenerator() {
        groups = null;
    }

    /**
     * Generate.
     * @return The population generated
     */
    public Population generate() {
        if (groups == null) {
            boolean ok = readXML("src\\main\\resources\\populations.xml");
            if (!ok) {
                return null;
            }
        }
        WorldState.reset();
        Random random = new Random();
        Population result = new Population();
        int maxYears = getMaximumAge();
        WorldState.getState().now().addYears(maxYears);
        int daysInAYear = TimeProperties.getInstance().daysInAYear();
        int ticksNow = WorldState.getState().now().getTicks();

        for (GroupInfo group : groups) {
            int variance = random.nextInt(
                    group.getSize().getMaximum()
                    - group.getSize().getMinimum());
            int total = group.getSize().getMinimum() + variance;
            LOGGER.info("Generating for race: ["
                    + String.valueOf(total)
                    + "] "
                    + group.race);
            Race race = RaceProperties.getInstance().get(group.getRace());
            int minAge = group.getAgeRange().getMinimum() * daysInAYear;
            int maxAge = group.getAgeRange().getMaximum() * daysInAYear;
            int varianceAge = maxAge - minAge;
            for (int count = 0; count < total; ++count) {
                String name = group.getRace();
                name += String.valueOf(count);
                Gender gender = Gender.MALE;
                if (random.nextDouble() < group.getFemaleRatio()) {
                    gender = Gender.FEMALE;
                }
                int ticks = random.nextInt(varianceAge) + minAge;
                Character c = new NonPlayerCharacter(
                        name,
                        race,
                        gender,
                        new WorldDate(ticksNow - ticks),
                        Family.create());
                result.addIndividual(c);
            }
        }

        LOGGER.info("Total characters created: "
                + String.valueOf(result.size()));
        return result;
    }

    /**
     * Read the initial population configuration from XML.
     * @param filename The file to load
     * @return True if load ok, else false
     */
    private boolean readXML(final String filename) {
        LOGGER.info("Loading config from " + filename);
        groups = new ArrayList<GroupInfo>();
        try {
            File file = new File(filename);
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(file);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getElementsByTagName("population");
            for (int count = 0; count < nodeList.getLength(); ++count) {
                Node item = nodeList.item(count);
                NodeList groupList = ((Element) item)
                        .getElementsByTagName("group");
                for (int index = 0; index < groupList.getLength(); ++index) {
                    Node group = groupList.item(index);
                    String race = group.getAttributes().getNamedItem("race")
                            .getTextContent();
                    int min = Integer.parseInt(group.getAttributes()
                            .getNamedItem("min").getTextContent());
                    int max = Integer.parseInt(group.getAttributes()
                            .getNamedItem("max").getTextContent());
                    double female = Double.parseDouble(group.getAttributes()
                            .getNamedItem("females").getTextContent());

                    Node ageNode = ((Element) group).getElementsByTagName("Age")
                            .item(0);
                    int ageMin = Integer.parseInt(ageNode.getAttributes()
                            .getNamedItem("min").getTextContent());
                    int ageMax = Integer.parseInt(ageNode.getAttributes()
                            .getNamedItem("max").getTextContent());

                    LOGGER.info("Loaded "
                            + race
                            + " Age["
                            + String.valueOf(ageMin)
                            + ".."
                            + String.valueOf(ageMax)
                            + "]");
                    groups.add(new GroupInfo(race,
                            new MinMaxPair<Integer>(min, max),
                            female,
                            new MinMaxPair<Integer>(ageMin, ageMax)));
                }
            }
        } catch (Exception e) {
            groups = null;
            return false;
        }
        return true;
    }

    /**
     * Get the maximum possible age.
     * @return The max age
     */
    private int getMaximumAge() {
        int max = 0;
        for (GroupInfo group : groups) {
            if (group.getAgeRange().getMaximum() > max) {
                max = group.getAgeRange().getMaximum();
            }
        }
        return max;
    }

    /**
     * Configuration for a race group in a population.
     * @author pcdejager
     */
    private final class GroupInfo {
        /**
         * Constructor.
         * @param r Race of group
         * @param s Minimum and maximum people in initial group
         * @param f The female ratio in the group
         * @param a The age information
         */
        public GroupInfo(
                final String r,
                final MinMaxPair<Integer> s,
                final double f,
                final MinMaxPair<Integer> a) {
            race = r;
            size = s;
            females = f;
            age = a;
        }

        /**
         * Get the race of the group.
         * @return The race
         */
        public String getRace() {
            return race;
        }

        /**
         * Get the minimum and maximum in initial group.
         * @return The minimum and maximum
         */
        public MinMaxPair<Integer> getSize() {
            return size;
        }

        /**
         * Get the female ratio.
         * @return The ratio
         */
        public double getFemaleRatio() {
            return females;
        }

        /**
         * Get the age information.
         * @return The age info
         */
        public MinMaxPair<Integer> getAgeRange() {
            return age;
        }

        /**
         * The race name.
         */
        private final String race;
        /**
         * The minimum and maximum size.
         */
        private final MinMaxPair<Integer> size;
        /**
         * The female ratio.
         */
        private final double females;
        /**
         * The age min max range.
         */
        private final MinMaxPair<Integer> age;
    }

    /**
     * The group information.
     */
    private ArrayList<GroupInfo> groups = null;
    /**
     * Logger.
     */
    private static final Logger LOGGER =
            Logger.getLogger(Generator.class.getName());
}
